/* ****************************************************************
 *
 * sb: MySql Command Line User Interface (Steam Boat Project)
 *     Note: I am going to port this to Postgres and Oracle too!
 *
 * Version 0.1 - Very Useful - and usually will not core dump.
 *
 * Work like a dog being taken for a walk, instead of an ox being
 * yoked to the plow.  The dog is having fun, the ox is not.
 *
 * A better command line interface to MySql.
 *  1. Use readline so you have command line editing (done)
 *  2. listings of your files to help find bad sql/queries. (started)
 *  3. A much better parser to reveil and suggest corrections to queries
 *  4. An automatic spelling checker - corrector - that will check table/column names and sugest correcitons.
 *  5. error handling on bad sql (Exit on error, Report and Continue, Exit after N errors).
 *  6. Readable output in a terminal window. (done)
 *  7. Useful in generating data/code for use in other tools.
 *
 */

/* Copyright (C) 2009-2010 Philip Schlump

   sb is free software; you can redistribute it
   and/or modify it under the terms of the GNU General Public License
   as published by the Free Software Foundation; either version 2, or
   (at your option) any later version.

   sb is distributed in the hope that it will be
   useful, but WITHOUT ANY WARRANTY; without even the implied warranty
   of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   The GNU General Public License is often shipped with GNU software, and
   is generally kept in a file called COPYING or LICENSE.  If you do not
   have a copy of the license, write to the Free Software Foundation,
   59 Temple Place, Suite 330, Boston, MA 02111 USA.
*/

/*
ToDo
	*0. implement html, csv and json output formats
	(Test the new output functions)
	1. Find the memroy leeks that we have introduced
	2. Look at the other output functions (prompt) etc.
	3.  rl_instream = ifp; (rl.c) - replace to use readlines capabilty to read from a file.

== == == == == == == == == == == == == == == == == == == == == == == ==
==                                                                   ==
==  Release 0.1                                                      ==
==                                                                   ==
== == == == == == == == == == == == == == == == == == == == == == == ==

0. Default Formatting: Create defaults in a .sb_init that specifiy default formatting for a table 
   so that "select X from T" will go find T in the default formatting and apply
   the default formatting rules for T.

1. Add per-column formatting - left just, right just (default based on type)
   Add truck of long text to X chars

== == == == == == == == == == == == == == == == == == == == == == == ==
==                                                                   ==
==  Release 0.2                                                      ==
==                                                                   ==
== == == == == == == == == == == == == == == == == == == == == == == ==

0. Multi line editing of SQL (just start VIM and edit away)

1. Lookup the names of tables / columns and add that to the readline fill in stuff

2. Lookup names of files when it is not a command so, > bla.sql can be filled in.
	FROM <table> Where Alias.Collumn

== == == == == == == == == == == == == == == == == == == == == == == ==
==                                                                   ==
==  Release 0.3                                                      ==
==                                                                   ==
== == == == == == == == == == == == == == == == == == == == == == == ==

0. Push data to mcached - "post a set of data up to the memory cache"
  refresh data in memory cache - pre-load data into memory cache.
	(On our web-service) using (our key)

	MD5(of SQL select with VALUES bound in) -> memcached as a key
	Pull back data.

1. Use our templating system to convert a HTML table into a templated
  HTML table.


== == == == == == == == == == == == == == == == == == == == == == == ==
==                                                                   ==
==  Release 0.4                                                      ==
==                                                                   ==
== == == == == == == == == == == == == == == == == == == == == == == ==



List of known defects:
000001. .sb_init trace command is not correctly turning on trace - or - not getting run at all?  don't know which.
	000002. nothing is working in the .sb_init routeins - not connect not anything - but they apear to run

000005. other output functions will mess up XML etc if not done properly (echo output)

-- Done --
000003. add an do_echo command
000004. chagne do_trace to turn trace on as well

--later--
0. recognize binary data and display it properly.
0. Reachability Testing
0. Autmatic re-testing
*/

#include <mysql/mysql.h> 
#include <mysql/errmsg.h> 

#if defined (HAVE_CONFIG_H)
#include <config.h>
#endif

#include <stdio.h>
#include <sys/types.h>
#include <string.h>
#include <sys/ioctl.h>
#include <regex.h>

#ifdef HAVE_STDLIB_H
#  include <stdlib.h>
#else 
extern void exit();
#endif

#include <sys/types.h>
#ifdef HAVE_SYS_FILE_H
#  include <sys/file.h>
#endif
#include <sys/stat.h>

#ifdef HAVE_UNISTD_H
#  include <unistd.h>
#endif

#include <fcntl.h>
#include <errno.h>

#if defined (HAVE_STRING_H)
#  include <string.h>
#else /* !HAVE_STRING_H */
#  include <strings.h>
#endif /* !HAVE_STRING_H */

#ifdef READLINE_LIBRARY
#  include "readline.h"
#  include "history.h"
#else
#  include <readline/readline.h>
#  include <readline/history.h>
#endif

#define MAX_BUF	4096
#define MAX_PATH_ELEMENTS 200

typedef struct nv_map {
	char *name;
	int value;
} NV_MAP;

#define FMT_TXT		1
#define FMT_CSV		2
#define FMT_HTML	3
#define FMT_XML		4
#define FMT_SQL		5
#define FMT_JSON	6
NV_MAP fmt[] = {
	{ "txt", 	FMT_TXT, 	},
	{ "csv",	FMT_CSV,	},
	{ "html",	FMT_HTML,	},
	{ "xml",	FMT_XML,	},
	{ "sql",	FMT_SQL,	},
	{ "json",	FMT_JSON,	},
//	{ "pdf",	FMT_pdf,	},
	{ (char *)NULL,	0, },			// value returned when not found, 0 or false - requires that FMT_* is 1...N
};

#define MAX_SPOOL_FILE 20
typedef struct st_spooler {
	int enabled;				// If true then this output destination is turned on
	FILE *fp;					// File if applicable
	int fmt;					// one of FMT_TXT, FMT_CSV, FMT_HTML, FMT_XML, FMT_SQL, FMT_JSON - may be more in future
	char *fn;
	char *path;
	int formatter_no;			// Index into the formatter table after a formatter has been found, -1 if empty, -2 if error
} SPOOLER;
SPOOLER spooler[MAX_SPOOL_FILE];

// externs
extern HIST_ENTRY **history_list ();
extern char *xmalloc ();
char *sh_get_env_value (const char *varname);
void x_assert(int tf);


// Forward declerations
int do_cd PARAMS((char *, char*));
int do_comment PARAMS((char *, char*));
int do_connect PARAMS((char *, char*));
int do_create PARAMS((char *, char*));
int do_delete PARAMS((char *, char*));
int do_disconnect PARAMS((char *, char*));
int do_drop PARAMS((char *, char*));
int do_help PARAMS((char *, char*));
int do_history PARAMS((char *, char*));
int do_insert PARAMS((char *, char*));
int do_load PARAMS((char *, char*));
int do_pwd PARAMS((char *, char*));
int do_quit PARAMS((char *, char*));
int do_run PARAMS((char *, char*));
int do_save PARAMS((char *, char*));
int do_select PARAMS((char *, char*));
int do_set PARAMS((char *, char*));
int do_sh PARAMS((char *, char*));
int do_show PARAMS((char *, char*));
int do_spool PARAMS((char *, char*));
int do_update PARAMS((char *, char*));
int do_use PARAMS((char *, char*));
int do_desc PARAMS((char *, char*));
int do_exec_path PARAMS((char *, char*));
int do_trace PARAMS((char *, char*));
int do_echo PARAMS((char *, char*));
#ifdef PUSH_CMD
int do_push_g PARAMS((char *, char*));
#endif

typedef int (*COMMAND_FUNC)(char *, char *);

typedef struct {
	char *name;				/* User printable name of the function. */
	// int (*func)(char *, char *);		/* Function to call to do the job. */
	COMMAND_FUNC func;
	char *doc;				/* Documentation for this function.  */
	int by_length;			/* if > 0 then # of char's for comand at beginning */
} COMMAND;

typedef struct any_val {
	union {
		int i_v;
		char c_v;
		char *s_v;
		int t_f_v;
	} u;
} ANY;

typedef int (*POST_SET)(int n);

typedef struct {
	char *name;
	char *value;
	char type;
	void *variable;
	ANY a;
	POST_SET func;
} NV_PAIR;

typedef struct {
	char *file_name;				
	int line_no;
	char *path;
	FILE *fp;
	int is_stdin;
} FILE_STACK;


#define MAX_LOGIN_BUF	128

#define MEM_TEST 1
#ifdef MEM_TEST
typedef struct st_mem {
	int line_no;
	char *func;
	int free_line_no;
	int outside;
	char *free_func;
	struct st_mem *prev;
	struct st_mem *next;
	int size;
	void *user_mem_ptr;		// Pointer to the memeory that belongs to the user.
} MEM;
MEM *head = NULL;
MEM *tail = NULL;
MEM *free_head = NULL;
MEM *free_tail = NULL;
#define x_malloc(s) 			mem_tst_malloc(s,_func_,__LINE__)
#define x_free(s)				mem_tst_free(s,_func_,__LINE__)
#define x_dupstr(s)				mem_tst_dupstr(s,_func_,__LINE__)
#define x_dump_lost_memory()	mem_tst_dump_lost_memory()
#define x_outside_malloc(s)		mem_test_outside_malloc(s)
void *mem_tst_malloc( int size, char *_func_, int line_no );
void mem_tst_free ( void *user, char *_func_, int line_no );
char *mem_tst_dupstr(char *s, char *_func_, int line_no );
void mem_tst_dump_lost_memory();
void mem_test_outside_malloc(void *s);
MEM *mem_tst_find ( void *user_mem, MEM *head );
#else
#define x_malloc(s) 			malloc(s)
#define x_free(s)				free(s)
#define x_dupstr(s)				dupstr(s)
#define x_dump_lost_memory()	
#define x_outside_malloc(line)
#endif

#define F_ACT_HDR		1
#define F_ACT_HDR_LINE	2
#define F_ACT_ROW		3
#define F_ACT_FTR		4
#define F_ACT_INIT		5
#define F_ACT_CLEANUP	6
#define F_ACT_PAGE		7

struct st_output_data;
typedef int (*OUTPUT_FUNC)(int f_act, struct st_output_data *d, MYSQL_ROW *row);
typedef struct {
	int fmt_name;
	char *destination;		// may be NULL for all destinations.
	OUTPUT_FUNC func;
} OUTPUT_FMT;

typedef struct st_output_data {
	int cur_width;
	int n_col;
	int *hdr_widths; // in chars
	int *col_widths; // in chars
	int char_to_px; // # of pix per char to convert widths to PX for fixed width displaying
	char **headers;	// Array of header strings
	int output_columns;		// display width in chars
	FILE *output_file_fp;	// the output file
	void *save_data;	// per-function saved dat a- created in F_ACT_INIT, cleaned up in F_ACT_CLEANUP.
} OUTPUT_DATA;

int output_func_csv ( int f_act, OUTPUT_DATA *d, MYSQL_ROW *row );
int output_func_html ( int f_act, OUTPUT_DATA *d, MYSQL_ROW *row );
int output_func_json ( int f_act, OUTPUT_DATA *d, MYSQL_ROW *row );
int output_func_sql ( int f_act, OUTPUT_DATA *d, MYSQL_ROW *row );
int output_func_txt ( int f_act, OUTPUT_DATA *d, MYSQL_ROW *row );
int output_func_txt_tty ( int f_act, OUTPUT_DATA *d, MYSQL_ROW *row );
int output_func_xml ( int f_act, OUTPUT_DATA *d, MYSQL_ROW *row );

COMMAND * find_command_by_length ( char *line );
COMMAND *find_command (char *);
FILE *stk_get_stack_fp();
char * dupstr (char *s);
char * find_file_path ( char *fn, char *sql_path, char *path_out );
char * name_of_nv_pair ( int value , NV_MAP *map );
OUTPUT_DATA * new_output_data (int num_fields,int columns);
void delete_output_data(OUTPUT_DATA *d);
char **fileman_completion PARAMS((const char *, int, int));
char *command_generator PARAMS((const char *, int));
char *read_from_file ( FILE **i_fp );
char *stk_get_stack_fn();
char *stk_get_stack_path();
char *stripwhite (char *);
ino_t getINodeOf ( char *file_name );
int do_trace  (char *arg, char *raw_line);
int lookup_nv_pair ( char *tag , NV_MAP *map );
int open_spool_file ( char *fn, char *fmt, int is_tty );
int run_sql (char *first_word, char *query);
int sb_re_match(const char *string, char *pattern);
int spool_file_availabel();
int stk_get_stack_line_no();
int stk_set_stack_line_no( int line_no );
int trace_func(char *func, int flag);
int trace_stack(char *func, int flag);
void close_spool_file ( int i );
void delete_spool_file();
void dump_callback_path();
void empty_trace_pat();
void free_memory();
void home_filename ( char *path_file_name, char *filename );
void init_spool_file();
void init_spool_file_stdout ();
void initialize_program();
void initialize_readline ();
void parse_connect ( char *connect_raw, char *host, char *un, char *pw, char *db );
void report_error(int err_code);
int match_output_type ( int need, int is_tty );
void spool_line_no();
void spool_line_txt ( char *out_line, char *nl );
void stk_dump_file_stack ( FILE *out );
void stk_pop_file ( char *fn, int *line_no, char *path, FILE **fp );
void stk_push_file ( char *fn, int line_no, char* path, FILE *fp, int is_stdin );
int test1();
int test2();
int test3();
int test4();

COMMAND commands[] = {
  { "!"			, do_sh			, "!cmd                Send command to shell for execution." 					, 1 },
  { "--"		, do_comment	, "--comment           Commend to end of line." 								, 2 },
  { "//"		, do_comment	, "//comment           Commend to end of line." 								, 2 },
  { "?"			, do_help		, "?                   Synonym for `help'" 										, 1 },
//  { "\."			, do_sh			, "!cmd                Send command to shell for execution." 					, 1 },
  { "@"			, do_run		, "@file.sql           Load and run a file. (@ is optional)" 					, 1 },
  { "bye"		, do_quit		, "bye                 Exit sb, Disconnet from MySql." 			 				, 0 },
  { "cd"		, do_cd			, "cd DIR              Change to directory DIR" 				 				, 0 },
  { "connect"	, do_connect	, "connect un/pw@db    Connect to a new database." 				 				, 0 },
  { "create"	, do_create		, "create ... ;        MySQL create ...;" 						 				, 0 },
  { "delete"	, do_delete		, "delete ... ;        MySQL delete ...;" 						 				, 0 },
  { "desc"		, do_desc		, "desc OBJ            Generate a description of the OBJ." 						, 0 },
  { "done"		, do_quit		, "done                Synonym for 'bye'. Exit sb, Disconnet from MySql." 		, 0 },
  { "drop"		, do_drop		, "drop ... ;          MySQL drop ...;" 										, 0 },
  { "exec_path"	, do_exec_path	, "exec_path           How did we get to the current line." 					, 0 },
  { "help"		, do_help		, "help                Show a short command summary." 							, 0 },
  { "history"	, do_history	, "history             Show command history." 									, 0 },
  { "insert"	, do_insert		, "insert ... ;        MySQL insert ...;" 										, 0 },
  { "load"		, do_load		, "load FN.SQL         Load a file with a single MySQL command." 				, 0 },
  { "logout"	, do_disconnect	, "logout              Synonym for 'bye'. Exit sb, Disconnect from MySql." 		, 0 },
  { "pwd"		, do_pwd		, "pwd                 Report the current working directory." 					, 0 },
  { "quit"		, do_quit		, "quit                Synonym for 'bye'. Exit sb, Disconnet from MySql." 		, 0 },
  { "save"		, do_save		, "save FN.SQL         Save current SQL statment into a file." 					, 0 },
  { "select"	, do_select		, "select ... ;        MySQL select ...;" 										, 0 },
  { "set"		, do_set		, "set VAR value       Set one of the internal flag values." 					, 0 },
  { "show"		, do_show		, "show ... ;          MySQL show ...;" 										, 0 },
  { "spool"		, do_spool		, "spool FILE.OUT      Send output to a file in the specified format." 			, 0 },
  { "update"	, do_update		, "update ... ;        MySQL update ...;" 										, 0 },
  { "use"		, do_use		, "use ... ;           Use the specified database." 							, 0 },
  { "echo"		, do_echo		, "echo ... ;          Echo the line typed in." 								, 0 },
  { "trace"		, do_trace		, "trace Function      Set funtions to be traced." 								, 0 },
#ifdef PUSH_CMD
  { "push_g"	, do_push_g		, "push table|select ...  Push to Google Apps/Google Mamcached the select."				, 0 },
#endif
  // { (char *)NULL, (int (*)(char *, char *))NULL, (char *)NULL }
  { (char *)NULL, (COMMAND_FUNC)NULL, (char *)NULL },
};

// Helper macros and definitions for tracing
#define MAX_CALL_DEPTH 200
int n_trace_stk = 0;
char *trace_stk[MAX_CALL_DEPTH];

#define IF_TRACE 	(trace_stack(_func_,0)&&trace_func(_func_,2))
#define ENT(f) 	static char *_func_ = f;if(trace_stack(_func_,1)&&trace_func(_func_,1)){fprintf(stderr,"In Func:%s Line:%d\n", f, __LINE__);}
#define EX() 		if(trace_stack(_func_,0)&&trace_func(_func_,0)){fprintf(stderr,"Ex Func:%s Line:%d\n", _func_, __LINE__);}
#define AT() 		if(trace_stack(_func_,2)&&trace_func(_func_,2)){fprintf(stderr,"At Line:%d\n", __LINE__);fflush(stderr);}
#define ATs(s) 		if(trace_stack(_func_,2)&&trace_func(_func_,2)){fprintf(stderr,"At Line:%d %s\n", __LINE__,s);fflush(stderr);}
#define PRMs(n,s)	if(trace_stack(_func_,2)&&trace_func(_func_,2)){fprintf(stderr,"\t%s=->%s<-\n",n,s);}
#define PRMn(n,s)	if(trace_stack(_func_,2)&&trace_func(_func_,2)){fprintf(stderr,"\t%s=%d\n",n,s);}
#define PRMc(n,s)	if(trace_stack(_func_,2)&&trace_func(_func_,2)){fprintf(stderr,"\t%s=[%c]\n",n,s);}
#define MAX_TRACE_PAT 200
char *trace_pat[MAX_TRACE_PAT];
int n_trace_pat = 0;

#define MAX_FILE_STACK 25

FILE *i_fp;
FILE *o_fp;
FILE_STACK *stk_fs_arr[MAX_FILE_STACK];
MYSQL mysql;
char *output_file = "";
char history_file_name[MAX_BUF];
int done = 0;
int is_connected = 0;
int stk_top = 0;

/* Types:
	's'		String Pointer - dynamically allocated.
	'i'		int
	't'		on/off true false int
*/
NV_PAIR settable_values[] = {
	{ "csv_seperator"		, ","				, 's',  0, },	// 0
	{ "input_file"			, ""				, 's',  0, },	// 1
	{ "debug"				, "off"				, 't',  0, },	// 2
	{ "line_no"				, "0"				, 'i',  0, },	// 3
	{ "line_no_on"			, "off"				, 't',  0, },	// 4
	{ "null"				, "NULL"			, 's',  0, },	// 5
	{ "output_format"		, "txt"				, 's',  0, },	// 6
	{ "prompt"				, "++> "			, 's',  0, },	// 7
	{ "spool_on"			, "off"				, 't',  0, },	// 8
	{ "trace"				, "on"				, 't',  0, },	// 9
	{ "sql_path"			, "."				, 's',  0, },	// 10
	{ "insert_table_name"	, "TABLE"			, 's',  0, },	// 11 - not used yet - for "sql" output type
	{ "program_name"		, "sb"				, 's',  0, },	// 12
	{ "spool_file"			, "out.lst"			, 's',  0, },	// 13
	{ "input_file_on"		, "off"				, 't',  0, },	// 14
	{ "connect_raw	"		, ""				, 's',  0, },	// 15
	{ (char *)0	},
};
#define CSV_SEPERATOR			(settable_values[0].a.u.s_v)
#define INPUT_FILE				(settable_values[1].a.u.s_v)
#define DEBUG					(settable_values[2].a.u.t_f_v)
#define LINE_NO					(settable_values[3].a.u.i_v)
#define LINE_NO_ON				(settable_values[4].a.u.t_f_v)
#define NULL_STR				(settable_values[5].a.u.s_v)
#define OUTPUT_FORMAT			(settable_values[6].a.u.s_v)
#define PROMPT					(settable_values[7].a.u.s_v)
#define SPOOL_ON				(settable_values[8].a.u.t_f_v)
#define TRACE					(settable_values[9].a.u.t_f_v)
#define SQL_PATH				(settable_values[10].a.u.s_v)
#define INSERT_TABLE_NAME		(settable_values[11].a.u.s_v)
#define PROGRAM_NAME			(settable_values[12].a.u.s_v)
#define SPOOL_FILE				(settable_values[13].a.u.s_v)
#define INPUT_FILE_ON			(settable_values[14].a.u.t_f_v)
#define CONNECT_RAW				(settable_values[15].a.u.s_v)



int main (int argc, char *argv[])
{
	char *line, *s;
	int c;
	int i_opt = 0;
	int o_opt = 0;
	int x_opt = 0;	// talk in XML totaly
	int s_opt = 0;  // skip running the .sb_init file
	int u_opt = 0;  // have a -u connect on command line.
	int T_opt = 0, test_no;
	ENT("main");

	initialize_program();
	PROGRAM_NAME = argv[0];
	i_fp = stdin; o_fp = stdout;

	while ((c = getopt (argc, argv, "i:o:u:dSxt:T:")) != -1)
	{
		switch (c)
		{
		case 'd':
			DEBUG = 1;
			break;
		case 'x':
			x_opt = 1;
			break;
		case 'S':
			x_opt = 1;
			break;
		case 'T':
			T_opt = 1;
			sscanf ( optarg, "%d", &test_no );
			break;
		case 't':
			TRACE = 1;
			trace_pat[n_trace_pat++] = x_dupstr( optarg );		// xyzzy -need to check globals
			break;
		case 'i':
			i_opt = 1;
			INPUT_FILE = x_dupstr( optarg );
			break;
		case 'o':
			o_opt = 1;
			output_file = x_dupstr( optarg );
			break;
		case 's':
			SPOOL_FILE = x_dupstr( optarg );
			SPOOL_ON = 1;
			open_spool_file ( SPOOL_FILE, "txt", 0 );
			break;
		case 'u':
			u_opt = 1;
			CONNECT_RAW = x_dupstr( optarg );
			break;
		case '?':
			if (optopt == 'c')
				fprintf (stderr, "Option -%c requires an argument.\n", optopt);
			else if (isprint (optopt))
				fprintf (stderr, "Unknown option `-%c'.\n", optopt);
			else
				fprintf (stderr, "Unknown option character `\\x%x'.\n", optopt);
			return 1;
		default:
			abort ();
		}
	}

	if ( T_opt )
	{
		int n_err = 255;
		switch ( test_no )
		{
		case 1: n_err=test1(); break;
		case 2: n_err=test2(); break;
		case 3: n_err=test3(); break;
		case 4: n_err=test4(); break;
		default:
			fprintf ( stderr, "Error (90688429): Invalid test number %d.  Should be between 1 and 3.\n", test_no );
		}
		exit(n_err);
	}

	if ( i_opt )
	{
		if ( ( i_fp = fopen ( INPUT_FILE, "r" ) ) == NULL )
		{
			fprintf ( stderr, "Error (96025851): Unable to open %s for input\n", INPUT_FILE );
			exit(3);
		}
		stk_push_file ( INPUT_FILE, 0, "./", i_fp, 0 /* is stdin:false */);
	}
	else
	{
		stk_push_file ( "stdin:", 0, "./", i_fp, 1 /* is stdin: true*/ );
	};

	if ( ! s_opt )
	{
		char buf[MAX_BUF];
		home_filename ( buf, ".sb_init" );
		ino_t i_node_cur_dir = -1;
		ino_t i_node_home = -1;
		if ( file_exists ( "./.sb_init" ) )	// and not in the home directory
		{
			i_node_cur_dir = getINodeOf ( "./.sb_init" );
			FILE *t_fp;
			// fprintf ( stderr, "Found .sb_init in current directory\n");
			if ( ( t_fp = fopen ( "./.sb_init", "r" ) ) == NULL )
			{
				fprintf ( stderr, "Unable to open %s for input.  File exists but not able to open it.\n", "./.sb_init" );
			}
			else
			{
				// fprintf ( stderr, "FiilePtr: %x\n", t_fp );
				stk_push_file ( "./.sb_init", 0, "./", t_fp, 0 /* is stdin: true*/ );
				i_fp = t_fp;
			}
		}
		// fprintf ( stderr, "Looking for the ~/.sb_init file and ./.sb_init file\n" );
		if ( file_exists ( buf ) )
		{
			FILE *t_fp;
			i_node_home = getINodeOf ( buf );
			if ( i_node_cur_dir != i_node_home )
			{
				// fprintf ( stderr, "Found .sb_init in home\n");
				if ( ( t_fp = fopen ( buf, "r" ) ) == NULL )
				{
					fprintf ( stderr, "Unable to open %s for input.  File exists but not able to open it.\n", buf );
				}
				else
				{
					// fprintf ( stderr, "FiilePtr: %x\n", t_fp );
					stk_push_file ( buf, 0, "./", t_fp, 0 /* is stdin: true*/ );
					i_fp = t_fp;
				}
			}
		}
	}

	mysql_init(&mysql); 

// xyzzy - use the -u parameter to connect
	
	if ( u_opt || strcmp ( CONNECT_RAW, "" ) != 0 )
	{
		char un[MAX_LOGIN_BUF], pw[MAX_LOGIN_BUF], db[MAX_LOGIN_BUF], host[MAX_LOGIN_BUF];
		parse_connect ( CONNECT_RAW, host, un, pw, db );
		if ( ! mysql_real_connect(&mysql,host,un,pw,db,0,NULL,0) )
		{
			fprintf ( stderr, "Error (94500896): Unable to connect as %s to %s on %s\n", un, db, host );
			exit(1);
		}
		is_connected = 1;
	}

	initialize_readline();	/* Bind our completer. */

	//AT();
	/* Loop reading and executing lines until the user quits. */
	while ( ! done )
	{
		AT();
		// fprintf ( stderr, "Line: %d at top; stk_top=%d\n", __LINE__, stk_top );
		if ( i_opt || stk_top > 1 )
		{
			line = read_from_file ( &i_fp );
		}
		else
		{
			if ( SPOOL_ON )
			{
				if ( strcmp ( INPUT_FILE, "--stdin--" ) != 0 )
				{
					if ( INPUT_FILE )
					{
						x_free ( INPUT_FILE );
					}
					INPUT_FILE = x_dupstr ( "--stdin--" );
				}
				spool_line_no();
				spool_line_txt ( PROMPT, "" );
			}
			line = readline ( PROMPT );
			x_outside_malloc(line);
		}
		LINE_NO++;
		stk_set_stack_line_no( LINE_NO );
		AT();

		if (!line)
			break;

		if ( SPOOL_ON )
		{
			spool_line_txt ( line, "\n" );
		}

		/* Remove leading and trailing whitespace from the line.
		Then, if there is anything left, add it to the history list
		and execute it. */
		AT();
		s = stripwhite (line);

		AT();
		if (*s)	// xyzzy - and is typed in by user?
		{
			if ( stk_top == 1 && ! i_opt )
				add_history (s);
		}
		if (*s)
		{
			run_command (s);
		}

		x_free(line);
		AT();
	}
	AT();
	write_history(history_file_name);
	do_disconnect("","");
	delete_spool_file();
	stk_pop_file ( NULL, &LINE_NO, NULL, &i_fp );
	free_memory();
	empty_trace_pat();
	x_dump_lost_memory();
	exit (0);
}

/* -------------------------------------------------------------------------------- */
// use file extention to get the format for the file.
char * get_output_format ( char *arg )
{
	char *h;

	// xyzzy - need to do better parsing of file names, this is just for now - a placeholder.
	h = strpbrk ( arg, "." );
	if ( h )
	{
		h++;
		if ( h && lookup_nv_pair ( h, fmt ) )
			return h;		
	}
	return "txt";
	
}

/* -------------------------------------------------------------------------------- */
ino_t getINodeOf ( char *file_name )
{
	struct stat s_stat;
	//ENT("getINodeOf");
	//PRMs("file name",file_name);
	if ( stat ( file_name, &s_stat ) == 0 )
	{
		//EX();
		return s_stat.st_ino;
	}
	//EX();
	return 0;
}

/* -------------------------------------------------------------------------------- */
char *read_from_file ( FILE **i_fp )
{
	char buf[MAX_BUF];
	int len;
	char fn[MAX_BUF];				// XYXXY, may need to copy into global
	char pth[MAX_BUF];				// XYXXY, may need to copy into global
	ENT("read_from_file");
	do
	{
		// fprintf ( stderr, "Loop top, %d\n", __LINE__ );
		if ( fgets ( buf, 4096, *i_fp ) )
		{
			len = strlen ( buf );
			if ( len > 0 && buf[len-1] == '\n' )
				buf[len-1] = '\0';
			// fprintf ( stderr, "Got a line, ->%s<-\n", buf );
			return x_dupstr ( buf );
		}
		else
		{
			// fprintf ( stderr, "fgets told us it was EOF, so Poping\n" );
			stk_pop_file ( fn, &LINE_NO, pth, i_fp );
			sprintf ( buf, "%s/%s", pth, fn );
			if ( INPUT_FILE )
			{
				x_free ( INPUT_FILE );
			}
			INPUT_FILE = x_dupstr ( buf );
			// fprintf ( stderr, "FiilePtr: %x\n", *i_fp );
		}
	}
	while ( stk_top > 0 );
	EX();
	return NULL;
}

/* -------------------------------------------------------------------------------- */
#ifndef MEM_TEST
char * dupstr (char *s)
{
	char *r;
	r = malloc(strlen (s) + 1);
	strcpy (r, s);
	return (r);
}
#endif

/* -------------------------------------------------------------------------------- */
/* Strip whitespace from the start and end of STRING.  Return a pointer into STRING. */
char * stripwhite (char *string)
{
	register char *s, *t;

	for (s = string; whitespace (*s); s++); /* no body */

	if (! *s )
		return (s);

	for ( t = s + strlen (s) - 1; (t > s && whitespace (*t)); t-- ); /* no body */
	*++t = '\0';

	return s;
}

/* -------------------------------------------------------------------------------- */
int run_command ( char *line )
{
	int i = 0, rv;
	COMMAND *command;
	char *word;
	ENT("run_command");

	command = find_command_by_length (line);
	if ( command )
	{
		i = command->by_length;
	}
	else
	{	
			// xyzzy - tokstr???
		/* Pick off the first word, whitespace delimited for the command */
		for ( i = 0; line[i] && whitespace (line[i]); i++);		/* no body */
		word = line + i;

		for (; line[i] && !whitespace (line[i]); i++);			/* no body */

		if (line[i])
			line[i++] = '\0';

		command = find_command (word);
	}

	if ( ! command )
	{
		fprintf (stderr, "->%s<-: No such command for sb.\n", word);
		return (-1);
	}

	/* Get argument to command, if any. */
	while (whitespace (line[i]))
		i++;

	word = line + i;

	/* Call the function. */
	rv = ((*(command->func)) (word,line));

	EX();
	return rv;
}

/* -------------------------------------------------------------------------------- */
COMMAND * find_command_by_length ( char *line )
{
	int i;
	ENT("find_command_by_length");

	for ( i = 0; commands[i].name; i++ )
	{
		if ( commands[i].by_length && strncmp (line, commands[i].name, commands[i].by_length ) == 0)
		{
			EX();
			return ( commands+i );
		}
	}

	EX();
	return ((COMMAND *)NULL);
}
/* -------------------------------------------------------------------------------- */
/*
 * Look up NAME as the name of a command, and return a pointer to that
 * command.  Return a NULL pointer if NAME isn't a command name.
 */
COMMAND * find_command ( char *name )
{
	int i;
	ENT("find_command");

	for (i = 0; commands[i].name; i++)
	{
		if (strcmp (name, commands[i].name) == 0)
		{
			EX();
			return ( commands+i );
		}
	}

	EX();
	return ((COMMAND *)NULL);
}

/* -------------------------------------------------------------------------------- */
int lookup_nv_pair ( char *tag , NV_MAP *map )
{
	int i;
	for ( i = 0; map[i].name; i++ )
	{
		if ( strcmp ( map[i].name, tag ) == 0 )
		{
			return map[i].value;
		}
	}
	return map[i].value;
}

/* -------------------------------------------------------------------------------- */
char * name_of_nv_pair ( int value , NV_MAP *map )
{
	int i;
	for ( i = 0; map[i].name; i++ )
	{
		if ( map[i].value == value )
		{
			return map[i].name;
		}
	}
	return (char *)NULL;
}
/* -------------------------------------------------------------------------------- */
/* **************************************************************** */
/*                                                                  */
/*                  Memory Lossage Testing (Double Entry)           */
/*                                                                  */
/* **************************************************************** */

#ifdef MEM_TEST
MEM *mem_tst_find ( void *user_mem, MEM *head )
{
	MEM *t;
	for ( t = head; t; t = t->next )
	{
		if ( t->user_mem_ptr == user_mem )
		{
			return t;
		}
	}
	return NULL;
}
void *mem_tst_malloc( int size, char *_func_, int line_no )
{
	MEM *cur = (MEM *)malloc ( sizeof ( struct st_mem ) );
	void *user = (void *)malloc ( size );
	x_assert(cur != NULL);
	cur->outside = 0;
	cur->next = NULL;
	cur->prev = tail;
	cur->line_no = line_no;
	cur->func = _func_; // has to be a static char *
	cur->size = size;
	cur->free_line_no = -1; // in use
	cur->free_func = NULL;
	cur->user_mem_ptr = user;
	if ( tail )
		tail->next = cur;
	else
		head = cur;
	tail = cur;
	return user;
}
void mem_tst_free ( void *user, char *_func_, int line_no )
{
	MEM *cur = mem_tst_find ( user, head );
	MEM *cur2 = mem_tst_find ( user, free_head );
	x_assert(user != NULL);
	x_assert(_func_ != NULL);
	x_assert(line_no > 1 && line_no < 1000000);
	if ( cur )
	{
		cur->free_line_no = line_no;
		cur->free_func = _func_;

		// -----------------------------------------------------------------------
		// detach from used list
		// -----------------------------------------------------------------------
		if ( cur == tail && cur == head )
		{
			head = tail = NULL;
			cur->next = cur->prev = NULL;
		}
		else if ( cur == tail )
		{
			tail = tail->prev;
			tail->next = NULL;
			cur->next = cur->prev = NULL;
		}
		else if ( cur == head )
		{
			head = head->next;
			head->prev = NULL;
			cur->next = cur->prev = NULL;
		}
		else
		{
			cur->prev->next = cur->next;
			cur->next->prev = cur->prev;
		}

		// -----------------------------------------------------------------------
		// attach to free list.
		// -----------------------------------------------------------------------
		cur->next = NULL;
		cur->prev = free_tail;
		cur->free_line_no = line_no;
		cur->free_func = _func_;
		if ( free_tail )
			free_tail->next = cur;
		else
			free_head = cur;
		free_tail = cur;
	}
	if ( cur2 )
	{
		if ( cur2->outside == 0 )
		{
			fprintf ( stderr
				, "Double free! Allocated at (%s:%d), freed at (%s:%d)(%s,%d) size %d\n"
				, cur2->func, cur2->line_no, cur2->free_func, cur2->free_line_no, _func_
				, line_no, cur2->size );
			dump_callback_path();
		}
	}
	free ( user );
}
void mem_test_outside_malloc(void *s)
{
	MEM *cur2 = mem_tst_find ( s, free_head );
	if ( cur2 )
		cur2->outside = 1;
}
char *mem_tst_dupstr(char *s, char *_func_, int line_no )
{
	int len;
	char *t = (char *)mem_tst_malloc ( (len=strlen(s)+1), _func_, line_no );
	strncpy ( t, s, len );
	return t;
}
void mem_tst_dump_lost_memory()
{
	MEM *t;
	for ( t = head; t; t = t->next )
	{
		fprintf ( stderr, "Lost %s %d of size %d\n", t->func, t->line_no, t->size );
	}
}
void x_assert(int tf)
{
	void *v;
	if ( ! tf )
	{
		fprintf ( stderr, "assert failded\n" );
		exit(1);
	}
}
#endif

/* **************************************************************** */
/*                                                                  */
/*                  Interface to Readline Completion                */
/*                                                                  */
/* **************************************************************** */


/* -------------------------------------------------------------------------------- */
/* Tell the GNU Readline library how to complete.  We want to try to complete
   on command names if this is the first word in the line, or on filenames
   if not. */
void initialize_readline ()
{
	/* Allow conditional parsing of the ~/.inputrc file. */
	rl_readline_name = "sb";

	/* Tell the completer that we want a crack first. */
	rl_attempted_completion_function = fileman_completion;

	read_history(history_file_name);
}

/* -------------------------------------------------------------------------------- */
/* Attempt to complete on the contents of TEXT.  START and END bound the
   region of rl_line_buffer that contains the word to complete.  TEXT is
   the word to complete.  We can use the entire contents of rl_line_buffer
   in case we want to do some simple parsing.  Return the array of matches,
   or NULL if there aren't any. */
char ** fileman_completion ( const char *text, int start, int end )
{
	char **matches;

	matches = (char **)NULL;

	/* If this word is at the start of the line, then it is a command
	 to complete.  Otherwise it is the name of a file in the current
	 directory. */
	if (start == 0)
	{
		matches = rl_completion_matches (text, command_generator);
	}

	return (matches);
}

/* -------------------------------------------------------------------------------- */
/* Generator function for command completion.  STATE lets us know whether
   to start from scratch; without any state (i.e. STATE == 0), then we
   start at the top of the list. */
char *command_generator ( const char *text, int state )
{
	static int list_index, len;
	char *name;
	ENT("command_generator");

	/* If this is a new word to complete, initialize now.  This includes
		saving the length of TEXT for efficiency, and initializing the index
		variable to 0. */
	if (!state)
	{
		list_index = 0;
		len = strlen (text);
	}
 
	/* Return the next name which partially matches from the command list. */
	while (name = commands[list_index].name)
    {
		list_index++;
		if (strncmp (name, text, len) == 0)
		{
			EX();
			return (x_dupstr(name));
		}
    }


	/* If no names matched, then return NULL. */
	EX();
	return ((char *)NULL);
}

void initialize_program()
{
	int i;
	ENT("initialize_program");

	home_filename ( history_file_name, ".sb_history" );
	init_spool_file();
	init_spool_file_stdout();

	for ( i = 0; settable_values[i].name; i++ )
	{
		switch ( settable_values[i].type )
		{
		case 'i':
			sscanf ( settable_values[i].value, "%d", &settable_values[i].a.u.i_v );
		break;
		case 't':
			settable_values[i].a.u.t_f_v = ( strcmp ( settable_values[i].value, "on" ) == 0 ) ? 1 : 0;
		break;
		case 's':
			settable_values[i].a.u.s_v = x_dupstr( settable_values[i].value );
		break;
		}
	}
	EX();
}

/* -------------------------------------------------------------------------------- */
void free_memory()
{
	int i;
	ENT("free_memory");
	for ( i = 0; settable_values[i].name; i++ )
	{
		if ( settable_values[i].type == 's' )
		{
			if ( settable_values[i].a.u.s_v )
				x_free( settable_values[i].a.u.s_v );
			settable_values[i].a.u.s_v = (char *)NULL;
		}
	}
	EX();
}



/* -------------------------------------------------------------------------------- */
int run_sql (char *first_word, char *arg)
{
	char * query;
	int err_code;
	ENT("run_sql");

	if ( ! is_connected )
	{
		fprintf ( stderr, "Error (3): You must connect to the database first\n" );
		EX();
		return 1;
	}

	query = (char *)x_malloc( 12 + strlen(arg) );
	sprintf ( query, "%s %s", first_word, arg );

	if ( err_code = mysql_real_query(&mysql,query,(unsigned int)strlen(query)) )
	{
		report_error(err_code);
	}

	x_free(query);

	EX();
	return err_code;
}

/* -------------------------------------------------------------------------------- */
/* Return true if file exists */
int file_exists ( char *file_name )
{
	struct stat s_stat;
	ENT("file_exists");
	PRMs("file name",file_name);
	if ( stat ( file_name, &s_stat ) == 0 )
	{
		EX();
		return 1;
	}
	EX();
	return 0;
}

/* -------------------------------------------------------------------------------- */
/* split up sql_path on ';' and then search the path for 'fn' if you find it then
   return a dynamically allocated copy of [paht]/fn.  Else null.
*/
char *find_file_path ( char *fn, char *sql_path, char *path_out )
{
	char * tok = strtok(sql_path, ";");
	char * path_element [ MAX_PATH_ELEMENTS ];
	int i, n_tok;
	char xfn [ MAX_BUF ];
	ENT("find_file_path");
	PRMs("fn",fn);
	PRMs("sql_path",sql_path);
	for (i = 0; i < MAX_PATH_ELEMENTS && tok != NULL; i++)
	{
		path_element[i] = tok;
		tok = strtok(NULL,";");
	}
	n_tok = i;
	//AT();

	for ( i = 0; i < n_tok; i++ )
	{
		//AT();
		if ( (strlen(path_element[i]) + strlen(fn) + 2) > MAX_BUF )
		{
			fprintf ( stderr, "Error (73422174): Path and file name exceed maximum (%d) (%s) (%s)\n", MAX_BUF
				, path_element[i], fn );
			exit(2);
		}
		//AT();
		//fprintf ( stderr, "%d\n", i );
		//fprintf ( stderr, "%s/%s\n", path_element[i], fn );
		//AT();
		sprintf ( xfn, "%s/%s", path_element[i], fn );
		if ( file_exists ( xfn ) )
		{
			strcpy ( path_out, path_element[i] );
			return x_dupstr ( xfn );
		}
		//AT();
	}
	EX();
	return NULL;
}

/* -------------------------------------------------------------------------------- */
void report_error(int err_code)
{
	switch (err_code)
	{
	case CR_COMMANDS_OUT_OF_SYNC:
		fprintf ( stderr, "Error (07831965): Commands were executed in an improper order.\n" );
		break;
	case CR_SERVER_GONE_ERROR:
		fprintf ( stderr, "Error (33694593): The MySQL server has gone away.\n" );
		break;
	case CR_SERVER_LOST:
		fprintf ( stderr, "Error (81455140): The connection to the server was lost during the query.\n" );
		break;
	case CR_UNKNOWN_ERROR:
		fprintf ( stderr, "Error (43662946): An unknown error occurred.\n" );
		break;
	}
}


/* -------------------------------------------------------------------------------- */
/* set one of the values in the "settable" table.
*/
void set_nv_pair ( char *name, char *value )
{
	int i;
	ENT("set_nv_pair");

	for ( i = 0; settable_values[i].name; i++ )
	{
		if ( strcmp ( name, settable_values[i].name ) == 0 )
		{
			switch ( settable_values[i].type )
			{
			case 'i':
				sscanf ( value, "%d", &settable_values[i].a.u.i_v );
			break;
			case 's':
				if ( settable_values[i].a.u.s_v )
					x_free( settable_values[i].a.u.s_v );
				settable_values[i].a.u.s_v = x_dupstr( value );
			break;
			case 't':
				settable_values[i].a.u.t_f_v = ( strcmp ( value, "on" ) == 0 ) ? 1 : 0;
			break;
			default:
				fprintf ( stderr, "Error (49831365): Intenral error - invalid data type in settable_values\n" );
			break;
			}
			goto SetIt;
		}
	}
	fprintf ( stderr, "Error (58016089): Unable to find [%s] to set\n", name );
SetIt:;

	EX();
}

/* -------------------------------------------------------------------------------- */
void home_filename ( char *path_file_name, char *filename )
{
	char *home;
	int home_len;
	int filename_len = strlen ( filename );
  
	home = sh_get_env_value ("HOME");

	if (home == NULL)
    {
		home = ".";
		home_len = 1;
    }
	else
	{
		home_len = strlen (home);
	}

	if ( filename_len + home_len > MAX_BUF )
	{
		fprintf ( stderr, "Error (55680092): File name too long\n" );
		exit(1);
	}

	sprintf ( path_file_name, "%s/%s", home, filename );
}

/* **************************************************************** */
/*                                                                  */
/*                  Testing routines and macros                     */
/*                                                                  */
/* **************************************************************** */
/* -------------------------------------------------------------------------------- */

#define IS_TRUE(x)	if(!(x)){fprintf(stderr,"test asertion failed line %d\n",__LINE__);fflush(stderr);n_err++;}

/* -------------------------------------------------------------------------------- */
int test1()
{
	char un[MAX_LOGIN_BUF], pw[MAX_LOGIN_BUF], db[MAX_LOGIN_BUF], host[MAX_LOGIN_BUF];
	int n_err = 0;
	ENT("test1");
	parse_connect ( strdup ( "un/pw/test@localhost" ), host, un, pw, db );
	if ( strcmp ( host, "localhost" ) != 0 )
	{
		fprintf ( stderr, "Error (38443281): parse_connect fails test 1a\n" );
		n_err++;
	}
	if ( strcmp ( un, "un" ) != 0 )
	{
		fprintf ( stderr, "Error (08697973): parse_connect fails test 1b\n" );
		n_err++;
	}
	if ( strcmp ( pw, "pw" ) != 0 )
	{
		fprintf ( stderr, "Error (25857776): parse_connect fails test 1c\n" );
		n_err++;
	}
	if ( strcmp ( db, "test" ) != 0 )
	{
		fprintf ( stderr, "Error (25624246): parse_connect fails test1d()\n" );
		n_err++;
	}
	if ( n_err == 0 )
	{
		fprintf ( stderr, "Success! test1() parse_connect()\n" );
	}
	else
	{
		fprintf ( stderr, "Error (50080114):  # of errors:%d Line:%d Func:parse_connect()\n", n_err, __LINE__ );
	}
	EX();
	return n_err;
}

/* -------------------------------------------------------------------------------- */
// kind of lame - runs some stuff - no real test. - Need to improve on this.
int test2()
{
	OUTPUT_DATA * d;
	fprintf ( stdout, "Test 2.\n" );
	//init_spool_file();
	//init_spool_file_stdout();
	//delete_spool_file();
	fprintf ( stderr, "Allocate and deallocate an OUTPUT_DATA type\n" );
	d = new_output_data (10,100);
	delete_output_data(d);
	fprintf ( stdout, "test2 ran.\n" );
	return 0;
}


/* -------------------------------------------------------------------------------- */
// test of match_output_type
int test3()
{
	int n, n_err = 0;
	n = match_output_type ( FMT_TXT, 1 );
	IS_TRUE(n==0);
	//fprintf ( stderr, "n=%d\n", n );

	n = match_output_type ( FMT_TXT, 0 );
	IS_TRUE(n==1);
	//fprintf ( stderr, "n=%d\n", n );

	n = match_output_type ( FMT_CSV, 0 );
	IS_TRUE(n==4);
	//fprintf ( stderr, "n=%d\n", n );

	n = match_output_type ( FMT_HTML, 0 );
	IS_TRUE(n==2);
	//fprintf ( stderr, "n=%d\n", n );

	n = match_output_type ( FMT_XML, 0 );
	IS_TRUE(n==3);
	//fprintf ( stderr, "n=%d\n", n );

	n = match_output_type ( FMT_SQL, 0 );
	IS_TRUE(n==5);
	//fprintf ( stderr, "n=%d\n", n );

	n = match_output_type ( FMT_JSON, 0 );
	IS_TRUE(n==6);
	//fprintf ( stderr, "n=%d\n", n );

	n = match_output_type ( 999, 0 );
	IS_TRUE(n==-1);
	//fprintf ( stderr, "n=%d\n", n );

	if ( n_err )
	{
		fprintf ( stderr, "Error (85318255):  # of errors:%d Line:%d Func:match_output_type\n", n_err, __LINE__ );
	}
	else
	{
		fprintf ( stderr, "Success! test3() match_output_type()\n" );
	}
	return n_err;
}

/* -------------------------------------------------------------------------------- */
int test4()
{
	int n, n_err = 0;
	// xyzzy - next test case
	if ( n_err )
	{
		fprintf ( stderr, "Error (50938758):  # of errors:%d Line:%d Func:xyzzy\n", n_err, __LINE__ );
	}
	else
	{
		fprintf ( stderr, "Success! test4() xyzzy()\n" );
	}
	return n_err;
}

/* -------------------------------------------------------------------------------- */
void parse_connect ( char *connect_raw, char *host, char *un, char *pw, char *db )
{
	// -u un/pw/db@localhost
	char *h;
	ENT("parse_connect");

	h = strpbrk ( connect_raw, "@" );
	if ( h && *h == '@' )
	{
		*h = '\0';
		h++;
		strncpy ( host, h, MAX_LOGIN_BUF );
	}
	else
	{
		strncpy ( host, "localhost", MAX_LOGIN_BUF );
	}

	h = strpbrk ( connect_raw, "/" );
	if ( h && *h == '/' )
	{
		*h = '\0';
		h++;
		strncpy ( un, connect_raw, MAX_LOGIN_BUF );
		connect_raw = h;
	}
	else
	{
		strncpy ( un, "root", MAX_LOGIN_BUF );
	}
	
	h = strpbrk ( connect_raw, "/" );
	if ( h && *h == '/' )
	{
		*h++ = '\0';
		strncpy ( pw, connect_raw, MAX_LOGIN_BUF );
		strncpy ( db, h, MAX_LOGIN_BUF );
	}
	else
	{
		strncpy ( pw, "", MAX_LOGIN_BUF );
		strncpy ( db, "test", MAX_LOGIN_BUF );
	}
	EX();	
}







/* **************************************************************** */
/*                                                                  */
/*                  spool file support routiens.                    */
/*                                                                  */
/* **************************************************************** */
/* -------------------------------------------------------------------------------- */
void init_spool_file()
{
	int i;
	SPOOL_ON = 0;
	for ( i = 0; i < MAX_SPOOL_FILE; i++ )
	{
		spooler[i].enabled = 1;
		spooler[i].fp = (FILE *)NULL;
		spooler[i].fmt = FMT_TXT;		// Default format
		spooler[i].fn = (char *)NULL;
		spooler[i].path = (char *)NULL;
		spooler[i].formatter_no = -3;
	}
}
/* -------------------------------------------------------------------------------- */
void init_spool_file_stdout()
{
	ENT("init_spool_file_stdout");
	spooler[0].fp = stdout;
	spooler[0].fmt = lookup_nv_pair ( "txt", fmt );
	spooler[0].enabled = 1;
	spooler[0].fn = x_dupstr ( "--stdout--" );
	spooler[0].path = NULL;				//xyzzy need to fix this.
	spooler[0].formatter_no = match_output_type ( spooler[0].fmt, 1 );
	EX();
}
/* -------------------------------------------------------------------------------- */
void delete_spool_file()
{
	int i;
	ENT("delete_spool_file");

	SPOOL_ON = 0;
	for ( i = 0; i < MAX_SPOOL_FILE; i++ )
	{
		if ( spooler[i].fp )
			close_spool_file ( i );
	}
	EX();
}
/* -------------------------------------------------------------------------------- */
int spool_file_availabel()
{
	int i;
	for ( i = 0; i < MAX_SPOOL_FILE; i++ )
	{
		if ( spooler[i].fp == (FILE *)NULL )
		{
			return i;
		}
	}
	return -1;
}
/* -------------------------------------------------------------------------------- */
void close_spool_file ( int i )
{
	ENT("close_spool_file");
	fclose ( spooler[i].fp );
	spooler[i].fp = (FILE *)NULL;	
	spooler[i].enabled = 1;
	spooler[i].fmt = FMT_TXT;
	if ( spooler[i].fn )
	{
		x_free ( spooler[i].fn );
		spooler[i].fn = (char *)NULL;
	}
	if ( spooler[i].path )
	{
		x_free ( spooler[i].path );
		spooler[i].path = (char *)NULL;
	}
	EX();
}
/* -------------------------------------------------------------------------------- */
int open_spool_file ( char *fn, char *a_fmt, int is_tty )
{
	int i;
	ENT("open_spool_file");
	for ( i = 0; i < MAX_SPOOL_FILE; i++ )
	{
		if ( spooler[i].fp == NULL )
		{
			if ( ( spooler[i].fp = fopen ( fn, "w" ) ) == NULL ) 
			{
				// Xyzzy - output current file name also - if not stdin.
				fprintf ( stderr, "Error (97001854): Unable to open %s for output (spooling).  Line: %d\n", fn, LINE_NO );
				EX();
				return 0;
			}
			spooler[i].fmt = lookup_nv_pair ( a_fmt, fmt );
			spooler[i].enabled = 1;
			spooler[i].fn = x_dupstr ( fn );
			spooler[i].path = NULL;				//xyzzy need to fix this.
			spooler[i].formatter_no = match_output_type ( spooler[i].fmt, is_tty );
			EX();
			return 1;
		}
	}
	// Xyzzy - need file name and line no added to message
	fprintf ( stderr, "Error (54457574): No more spool files availabe.  Limit is %d open at a time\n", MAX_SPOOL_FILE );
	EX();
	return 0;
}









/* **************************************************************** */
/*                                                                  */
/*                  File Stack for @fn and include proc             */
/*                                                                  */
/* **************************************************************** */
void stk_push_file ( char *fn, int line_no, char* path, FILE *fp, int is_stdin )
{
	int i;
	ENT("stk_push_file");
	if ( stk_top < MAX_FILE_STACK )
	{
		stk_fs_arr[stk_top] = x_malloc( sizeof ( FILE_STACK ) );
		stk_fs_arr[stk_top]->file_name = x_dupstr( fn );
		stk_fs_arr[stk_top]->path = x_dupstr ( path );
		stk_fs_arr[stk_top]->line_no = line_no;
		stk_fs_arr[stk_top]->fp = fp;
		stk_fs_arr[stk_top]->is_stdin = is_stdin;
		stk_top++;
	}
	else
	{
		fprintf ( stderr, "Error (97700216): Fatal: File nesting too deep, maximum %d reached.\n", MAX_FILE_STACK );
		for ( i = 0; i < stk_top; i++ )
		{
			stk_dump_file_stack ( stderr );
		}
		EX();
		exit(1);
	}
	EX();
}
void stk_pop_file ( char *fn, int *line_no, char *path, FILE **fp )
{
	ENT("stk_pop_file");
	if ( stk_top > 0 )
	{
		--stk_top;
		if ( fn )
		{
			strncpy ( fn, stk_fs_arr[stk_top]->file_name, MAX_BUF );
		}
		if ( path )
		{
			strncpy ( path, stk_fs_arr[stk_top]->path, MAX_BUF );
		}
		*line_no = stk_fs_arr[stk_top]->line_no;
		*fp = stk_fs_arr[stk_top]->fp;

		x_free( stk_fs_arr[stk_top]->file_name );
		x_free( stk_fs_arr[stk_top]->path );
		x_free( stk_fs_arr[stk_top] );
		stk_fs_arr[stk_top] = NULL;
	}
	else
	{
		fprintf ( stderr, "Error (41896860): Fatal: Atempt to pop/close file when there are no files on the stack.\n" );
		EX();
		exit(1);
	}
	EX();
}
char *stk_get_stack_fn()
{
	if ( stk_top > 1 )
		return stk_fs_arr[stk_top-1]->file_name;
	return NULL;
}
char *stk_get_stack_path()
{
	if ( stk_top > 1 )
		return stk_fs_arr[stk_top-1]->path;
	return NULL;
}
FILE *stk_get_stack_fp()
{
	if ( stk_top > 1 )
		return stk_fs_arr[stk_top-1]->fp;
	return NULL;
}
int stk_get_stack_line_no()
{
	if ( stk_top > 1 )
		return stk_fs_arr[stk_top-1]->line_no;
	return 0;
}
int stk_set_stack_line_no( int line_no )
{
	if ( stk_top > 1 )
		return stk_fs_arr[stk_top-1]->line_no = line_no;
	return line_no;
}
void stk_dump_file_stack ( FILE *out )
{
	// for error deubging - track back the locaiton in the stack so it is easy to find where a bad @ is from
	int i;
	for ( i = 0; i < stk_top; i++ )
	{
		fprintf ( out, "\t%s/%s line %d\n", stk_fs_arr[i]->path, stk_fs_arr[i]->file_name, stk_fs_arr[i]->line_no );
	}
}





/* **************************************************************** */
/*                                                                  */
/*                  do_* functions: Implement commands.             */
/*                                                                  */
/* **************************************************************** */

/* -------------------------------------------------------------------------------- */
int do_exec_path (char *arg, char *raw_line)
{
	ENT("do_exec_path");
	stk_dump_file_stack ( stdout );
	EX();
	return 1;
}
/* -------------------------------------------------------------------------------- */
int do_cd (char *arg, char *raw_line)
{
	ENT("do_cd");
	chdir ( arg );
	EX();
	return 1;
}
/* -------------------------------------------------------------------------------- */
int do_pwd (char *arg, char *raw_line)
{
	char dir[1024], *s;
	ENT("do_cd");

	s = getcwd (dir, sizeof(dir) - 1);
	if (s == 0)
	{
		printf ("Error getting pwd: %s\n", dir);
		EX();
		return 1;
	}

	printf ("%s\n", dir);
	EX();
	return 1;
}

/* -------------------------------------------------------------------------------- */
/* XYZZY - change this to do a negative offset from end and return the pointer to middle of string */
char dup_char_buf[MAX_BUF];
char *dup_char(int n_dup, char c)
{
	int i;
	register char *s = dup_char_buf;
	ENT("dup_char");
	PRMn("n_dup",n_dup);
	PRMc("c",c);
	for ( i = 0; i < n_dup && i < MAX_BUF-1; i++ )
	{
		*s++ = c;
	}
	*s = '\0';
	EX();
	return dup_char_buf;
}


/* -------------------------------------------------------------------------------- */
/*
 * Match string against the extended regular expression in
 * pattern, treating errors as no match.
 *
 * Return 1 for match, 0 for no match.
 */
int sb_re_match(const char *string, char *pattern)
{
    int    status;
    regex_t    re;


    if (regcomp(&re, pattern, REG_EXTENDED|REG_NOSUB) != 0)
	{
        return(0);      /* Report error. */
    }
    status = regexec(&re, string, (size_t) 0, NULL, 0);
    regfree(&re);
    if (status != 0)
	{
        return(0);      /* Report error. */
    }
    return(1);
}

/* -------------------------------------------------------------------------------- */
void dump_callback_path()
{
	int i;
	char com = '\t';
	for ( i = 0; i < n_trace_stk; i++ )
	{
		fprintf ( stderr, "%c%s", com, trace_stk[i] );
		com = ',';
	}
	fprintf ( stderr, "\n");
}
/* -------------------------------------------------------------------------------- */
int trace_stack(char *func, int flag)
{
	int i;

	switch ( flag )
	{
	case 1: /*enter*/
		if ( n_trace_stk < MAX_CALL_DEPTH )
		{
			trace_stk[n_trace_stk++] = func;
		}
	break;
	case 0: /*exit*/
		if ( n_trace_stk > 0 )
		{
			n_trace_stk--;
		}
	break;
	case 2: /*disregard*/
	default:
	break;
	}
	return TRACE;
}
/* -------------------------------------------------------------------------------- */
int trace_func(char *func, int flag)
{
	int i;
	for ( i = 0; i < n_trace_pat; i++ )
	{
		// if ( strcmp ( trace_pat[i], func ) == 0 )
		if ( sb_re_match ( func, trace_pat[i] ) )
		{
			return 1;
		}
	}
	return 0;
}

/* -------------------------------------------------------------------------------- */
void getTerminalSize ( int *lines, int *columns )
{
    struct winsize w;
	ENT("getTerminalSize");

    ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);

    //printf ("lines %d\n", w.ws_row);
    //printf ("columns %d\n", w.ws_col);
	*lines = w.ws_row;
	*columns = w.ws_col;

	EX();
}
/* returns true if the file we are reading/from writing to is stdin - top level */
int is_stdin ( )
{
	ENT("is_stdin");
	// xyzzy - needs tocheck stack top to see if it is stdin
	
	EX();
	return 1;
}

/* -------------------------------------------------------------------------------- */
/*
typedef struct st_mysql_field {
  char *name;                 / * Name of column * /
  char *org_name;             / * Original column name, if an alias * /
  char *table;                / * Table of column if column was a field * /
  char *org_table;            / * Org table name, if table was an alias * /
  char *db;                   / * Database for table * /
  char *catalog;	      / * Catalog for table * /
  char *def;                  / * Default value (set by mysql_list_fields) * /
  unsigned long length;       / * Width of column (create length) * /
  unsigned long max_length;   / * Max width for selected set * /
  unsigned int name_length;
  unsigned int org_name_length;
  unsigned int table_length;
  unsigned int org_table_length;
  unsigned int db_length;
  unsigned int catalog_length;
  unsigned int def_length;
  unsigned int flags;         / * Div flags * /
  unsigned int decimals;      / * Number of decimals in field * /
  unsigned int charsetnr;     / * Character set * /
  enum enum_field_types type; / * Type of field. See mysql_com.h for types * /
} MYSQL_FIELD;
enum enum_field_types {
	MYSQL_TYPE_DECIMAL
,	MYSQL_TYPE_TINY
,	MYSQL_TYPE_SHORT
,	MYSQL_TYPE_LONG
,	MYSQL_TYPE_FLOAT
,	MYSQL_TYPE_DOUBLE
,	MYSQL_TYPE_NULL
,	MYSQL_TYPE_TIMESTAMP
,	MYSQL_TYPE_LONGLONG
,	MYSQL_TYPE_INT24
,	MYSQL_TYPE_DATE
,	MYSQL_TYPE_TIME
,	MYSQL_TYPE_DATETIME
,	MYSQL_TYPE_YEAR
,	MYSQL_TYPE_NEWDATE
,	MYSQL_TYPE_VARCHAR
,	MYSQL_TYPE_BIT
,	MYSQL_TYPE_NEWDECIMAL=246
,	MYSQL_TYPE_ENUM=247
,	MYSQL_TYPE_SET=248
,	MYSQL_TYPE_TINY_BLOB=249
,	MYSQL_TYPE_MEDIUM_BLOB=250
,	MYSQL_TYPE_LONG_BLOB=251
,	MYSQL_TYPE_BLOB=252
,	MYSQL_TYPE_VAR_STRING=253
,	MYSQL_TYPE_STRING=254
,	MYSQL_TYPE_GEOMETRY=255
};
*/
void spool_line_no()
{
	int i;
	for ( i = 0; i < MAX_SPOOL_FILE; i++ )
	{
		if ( spooler[i].enabled && spooler[i].fp != NULL )
		{
			if ( INPUT_FILE_ON )
			{
				fprintf ( spooler[i].fp, "%s ", INPUT_FILE );
			}
			if ( LINE_NO_ON )
			{
				fprintf ( spooler[i].fp, "%4d ", LINE_NO );
			}
		}
	}
}

void spool_line_txt ( char *out_line, char *nl )
{
	int i;
	for ( i = 0; i < MAX_SPOOL_FILE; i++ )
	{
		if ( spooler[i].enabled && spooler[i].fp != NULL )
		{
			fprintf ( spooler[i].fp, "%s%s", out_line, nl );
		}
	}
}

int err_new_delete_output_data = 0;

OUTPUT_DATA * new_output_data (int num_fields,int columns)
{
	int i;
	OUTPUT_DATA *d;
	ENT("new_output_data");
	d = (OUTPUT_DATA*)x_malloc ( sizeof ( OUTPUT_DATA ) );
	if ( err_new_delete_output_data ) fprintf ( stderr, "0x%08x new OUTPUT_DATA\n", d );
	d->cur_width = 0;
	d->n_col = num_fields;
	d->hdr_widths = (int *)x_malloc ( num_fields * sizeof ( int ) );
	if ( err_new_delete_output_data ) fprintf ( stderr, "0x%08x new d->hdr_widths\n", d->hdr_widths );
	d->col_widths = (int *)x_malloc ( num_fields * sizeof ( int ) );
	if ( err_new_delete_output_data ) fprintf ( stderr, "0x%08x new d->col_widths\n", d->col_widths );
	d->char_to_px = 22;
	d->headers = (char **)x_malloc ( num_fields * sizeof ( char * ) );
	if ( err_new_delete_output_data ) fprintf ( stderr, "0x%08x new d->headers\n", d->headers );
	d->save_data = NULL;
	d->output_columns = columns;
	for ( i = 0; i < num_fields; i++ )
	{
		d->hdr_widths[i] = 0;
		d->col_widths[i] = 0;
		d->headers[i] = NULL;
	}
	if ( err_new_delete_output_data ) fprintf ( stderr, "0x%08x at-bot d->headers\n", d->headers );

	EX();
	return d;
}

void delete_output_data(OUTPUT_DATA *d)
{
	int i;
	ENT("delete_output_data");

	if ( err_new_delete_output_data ) fprintf ( stderr, "0x%08x at-top d->headers\n", d->headers );

	if ( err_new_delete_output_data ) fprintf ( stderr, "0x%08x d->hdr_widths\n", d->hdr_widths );
	x_free(d->hdr_widths);
	if ( err_new_delete_output_data ) fprintf ( stderr, "0x%08x d->col_widths\n", d->col_widths );
	x_free(d->col_widths);
	if ( err_new_delete_output_data ) fprintf ( stderr, "0x%08x at-mid:%d d->headers\n", d->headers, __LINE__ );
	for ( i = 0; i < d->n_col; i++ )
	{
		if ( d->headers[i] )
		{
			if ( err_new_delete_output_data ) fprintf ( stderr, "0x%08x d->headers[%d]\n", d->headers[i], i );
			x_free(d->headers[i]);
		}
	}
	if ( err_new_delete_output_data ) fprintf ( stderr, "0x%08x d->headers\n", d->headers );
	x_free(d->headers);
	if ( err_new_delete_output_data ) fprintf ( stderr, "0x%08x OUTPUT_DATA\n", d );
	x_free(d);
	EX();
}

OUTPUT_FMT output_fmt_f[] = {
	{ FMT_TXT	, "tty"	, output_func_txt_tty, },
	{ FMT_TXT	, NULL	, output_func_txt, },
	{ FMT_HTML	, NULL	, output_func_html, },
	{ FMT_XML	, NULL	, output_func_xml, },
	{ FMT_CSV	, NULL	, output_func_csv, },
	{ FMT_SQL	, NULL	, output_func_sql, },
	{ FMT_JSON	, NULL	, output_func_json, },
	{ 0, NULL	, NULL, },
};

int match_output_type ( int need, int is_tty )
{
	int i;
	ENT("match_output_type");
	for ( i = 0; output_fmt_f[i].fmt_name; i++ )
	{
		// fprintf ( stderr, "match_output_type: need=%d is_tty=%d i=%d\n", need, is_tty, i );
		if ( need == output_fmt_f[i].fmt_name 
			&& is_tty 
			&& output_fmt_f[i].destination != NULL 
			&& strcmp ( "tty", output_fmt_f[i].destination ) == 0
		)
		{
			EX();
			return i;
		}
		if ( need == output_fmt_f[i].fmt_name && !is_tty && output_fmt_f[i].destination == NULL )
		{
			EX();
			return i;
		}
	}
	EX();
	return -1;
}

int output_func_txt ( int f_act, OUTPUT_DATA *d, MYSQL_ROW *row )
{
	struct our_data {
		int width;		// current amout of space used on the current line
	};
	char buf[MAX_BUF];
	char buf2[MAX_BUF];
	char output_line[MAX_BUF];
	struct our_data *dd;
	int i;
	ENT("ouptut_func_txt");
	PRMn("f_act",f_act);

	switch ( f_act )
	{
	case F_ACT_HDR:
		AT();
		dd = (struct our_data *)(d->save_data);
		output_line[0] = '\0';
		for ( i = 0; i < d->n_col; i++ )
		{
			strncpy ( buf, d->headers[i], MAX_BUF );
			if ( d->col_widths[i] < MAX_BUF )
				buf[d->col_widths[i]] = '\0';
			sprintf ( buf2, "%*s |",d->col_widths[i], buf);
			strcat ( output_line, buf2 );
		}
		fprintf ( d->output_file_fp, "%s\n", output_line );
		fflush ( d->output_file_fp );
	break;
	case F_ACT_HDR_LINE:
		AT();
		dd = (struct our_data *)(d->save_data);
		output_line[0] = '\0';
		for ( i = 0; i < d->n_col ; i++ )
		{
			sprintf ( buf2, "%s+", dup_char(d->col_widths[i]+1,'=') );
			strcat ( output_line, buf2 );
		}
		fprintf ( d->output_file_fp, "%s\n", output_line );
	break;
	case F_ACT_ROW:
		AT();
		dd = (struct our_data *)(d->save_data);
		output_line[0] = '\0';
		for ( i = 0; i < d->n_col ; i++ )
		{
			// CSV: printf("%s%s",(row[i]?row[i]:NULL_STR),CSV_SEPERATOR);
			sprintf ( buf, "%*s |", d->col_widths[i], ((*row)[i]?(*row)[i]:NULL_STR) );
			strcat ( output_line, buf );
		}
		printf ( "%s\n", output_line );
		fflush ( stdout );
	break;
	case F_ACT_FTR:
	break;
	case F_ACT_INIT:
		AT();
		d->save_data = (void *)x_malloc ( sizeof ( struct our_data ) );
		dd = (struct our_data *)(d->save_data);
		dd->width = 0;	
	break;
	case F_ACT_CLEANUP:
		AT();
		dd = (struct our_data *)(d->save_data);
		x_free ( dd );
	break;
	case F_ACT_PAGE:
	break;
	}
	EX();
}

int output_func_csv ( int f_act, OUTPUT_DATA *d, MYSQL_ROW *row )
{
	char buf[MAX_BUF];
	char buf2[MAX_BUF];
	char output_line[MAX_BUF];
	struct our_data *dd;
	int i;
	ENT("ouptut_func_csv");
	PRMn("f_act",f_act);

	switch ( f_act )
	{
	case F_ACT_HDR:
		AT();
		output_line[0] = '\0';
		for ( i = 0; i < d->n_col; i++ )
		{
			strncpy ( buf, d->headers[i], MAX_BUF );		// xyzzy - cleanup - quote text if necessary, no COMMA at end
			sprintf ( buf2, "%s%s", buf, CSV_SEPERATOR);
			strcat ( output_line, buf2 );
		}
		fprintf ( d->output_file_fp, "%s\n", output_line );
		fflush ( d->output_file_fp );
		AT();
	break;
	case F_ACT_HDR_LINE:
	break;
	case F_ACT_ROW:
		AT();
		output_line[0] = '\0';
		for ( i = 0; i < d->n_col ; i++ )
		{
			sprintf ( buf, "%s%s", ((*row)[i]?(*row)[i]:""), CSV_SEPERATOR );
			strcat ( output_line, buf );
		}
		printf ( "%s\n", output_line );
		fflush ( stdout );
	break;
	case F_ACT_FTR:
	break;
	case F_ACT_INIT:
	break;
	case F_ACT_CLEANUP:
	break;
	case F_ACT_PAGE:
	break;
	}
	EX();
}

int output_func_html ( int f_act, OUTPUT_DATA *d, MYSQL_ROW *row )
{
	switch ( f_act )
	{
	case F_ACT_HDR:
	break;
	case F_ACT_HDR_LINE:
	break;
	case F_ACT_ROW:
	break;
	case F_ACT_FTR:
		// ignore - no footer on text output.
	break;
	case F_ACT_INIT:
	break;
	case F_ACT_CLEANUP:
	break;
	case F_ACT_PAGE:
	break;
	}
}

int output_func_json ( int f_act, OUTPUT_DATA *d, MYSQL_ROW *row )
{
	struct our_data {
		int width;		// current amout of space used on the current line
		int is_first;
	};
	char buf[MAX_BUF];
	char buf2[MAX_BUF];
	char output_line[MAX_BUF];
	struct our_data *dd;
	int i, w;
	ENT("ouptut_func_json");
	PRMn("f_act",f_act);

	switch ( f_act )
	{
	case F_ACT_HDR:
		printf ( "[\n" );
	break;
	case F_ACT_HDR_LINE:
	break;
	case F_ACT_ROW:
		AT();
		dd = (struct our_data *)(d->save_data);
		output_line[0] = '\0';
		if ( dd->is_first )
			strcat ( output_line, ",[" );
		else
			strcat ( output_line, " [" );
		for ( i = 0; i < d->n_col && w < d->output_columns; i++ )
		{
			sprintf ( buf, "\"%s\",", ((*row)[i]?(*row)[i]:"") );		// xyzzy - Escape quotes!
			strcat ( output_line, buf );
		}
		strcat ( output_line, "]" );
		printf ( "%s\n", output_line );
		fflush ( stdout );
		dd->is_first = 0;	
	break;
	case F_ACT_FTR:
		printf ( "]\n" );
	break;
	case F_ACT_INIT:
		AT();
		d->save_data = (void *)x_malloc ( sizeof ( struct our_data ) );
		dd = (struct our_data *)(d->save_data);
		dd->width = 0;	
		dd->is_first = 1;	
	break;
	case F_ACT_CLEANUP:
		AT();
		dd = (struct our_data *)(d->save_data);
		x_free ( dd );
	break;
	case F_ACT_PAGE:
	break;
	}
	EX();
}

int output_func_sql ( int f_act, OUTPUT_DATA *d, MYSQL_ROW *row )
{
	switch ( f_act )
	{
	case F_ACT_HDR:
	break;
	case F_ACT_HDR_LINE:
	break;
	case F_ACT_ROW:
	break;
	case F_ACT_FTR:
		// ignore - no footer on text output.
	break;
	case F_ACT_INIT:
	break;
	case F_ACT_CLEANUP:
	break;
	case F_ACT_PAGE:
	break;
	}
}

int output_func_xml ( int f_act, OUTPUT_DATA *d, MYSQL_ROW *row )
{
	switch ( f_act )
	{
	case F_ACT_HDR:
	break;
	case F_ACT_HDR_LINE:
	break;
	case F_ACT_ROW:
	break;
	case F_ACT_FTR:
		// ignore - no footer on text output.
	break;
	case F_ACT_INIT:
	break;
	case F_ACT_CLEANUP:
	break;
	case F_ACT_PAGE:
	break;
	}
}

int output_func_txt_tty ( int f_act, OUTPUT_DATA *d, MYSQL_ROW *row )
{
	struct our_data {
		int width;		// current amout of space used on the current line
		int not_all_too_wide;
	};
	char buf[MAX_BUF];
	char buf2[MAX_BUF];
	char output_line[MAX_BUF];
	struct our_data *dd;
	int i, w;
	ENT("ouptut_func_txt_tty");
	PRMn("f_act",f_act);

	switch ( f_act )
	{
	case F_ACT_HDR:
		AT();
		PRMn("d->n_col",d->n_col);
		if ( IF_TRACE )
		{
			fprintf ( stderr, "%3s: %4s %4s\n", "c#", "c.w", "hdr" );
			for ( i = 0; i < d->n_col; i++ )
			{
				fprintf ( stderr, "%3d: %4d %4d\n", i, d->col_widths[i], d->hdr_widths[i] );
			}
		}
		dd = (struct our_data *)(d->save_data);
		AT();
		output_line[0] = '\0';
		w = 0;
		for ( i = 0; i < d->n_col; i++ )
		{
			AT();
			strncpy ( buf, d->headers[i], MAX_BUF );
			if ( d->col_widths[i] < MAX_BUF )
				buf[d->col_widths[i]] = '\0';
			sprintf ( buf2, "%*s |",d->col_widths[i], buf);
			AT();
			w += strlen ( buf2 ) + 2;
			if ( w < d->output_columns )
			{
				strcat ( output_line, buf2 );
			}
			else
			{
				dd->not_all_too_wide = 1;
			}
			AT();
		}
		AT();
		fprintf ( d->output_file_fp, "%s\n", output_line );
		fflush ( d->output_file_fp );
		AT();
	break;
	case F_ACT_HDR_LINE:
		AT();
		dd = (struct our_data *)(d->save_data);
		output_line[0] = '\0';
		w = 0;
		for ( i = 0; i < d->n_col && w < d->output_columns; i++ )
		{
			sprintf ( buf2, "%s+", dup_char(d->col_widths[i]+1,'=') );
			w += strlen ( buf2 )+2;
			if ( w < d->output_columns )
			{
				strcat ( output_line, buf2 );
			}
		}
		fprintf ( d->output_file_fp, "%s\n", output_line );
	break;
	case F_ACT_ROW:
		AT();
		dd = (struct our_data *)(d->save_data);
		w = 0;
		output_line[0] = '\0';
		for ( i = 0; i < d->n_col && w < d->output_columns; i++ )
		{
			// CSV: printf("%s%s",(row[i]?row[i]:NULL_STR),CSV_SEPERATOR);
			sprintf ( buf, "%*s |", d->col_widths[i], ((*row)[i]?(*row)[i]:NULL_STR) );
			w += strlen ( buf )+2;
			if ( w < d->output_columns )
			{
				// printf ( "%s", buf );
				strcat ( output_line, buf );
			}
		}
		printf ( "%s\n", output_line );
		fflush ( stdout );
	break;
	case F_ACT_FTR:
		AT();
		dd = (struct our_data *)(d->save_data);
		if ( dd->not_all_too_wide )
		{
			fprintf ( d->output_file_fp, "**note** not all columns were printed because your terminal is not wide enough.\n" );
		}
	break;
	case F_ACT_INIT:
		AT();
		d->save_data = (void *)x_malloc ( sizeof ( struct our_data ) );
		dd = (struct our_data *)(d->save_data);
		dd->width = 0;	
		dd->not_all_too_wide = 0;
	break;
	case F_ACT_CLEANUP:
		AT();
		dd = (struct our_data *)(d->save_data);
		x_free ( dd );
	break;
	case F_ACT_PAGE:
		AT();
		// xyzzy - need to do something?
	break;
	}
	EX();
}

void call_spoolers ( int f_act, OUTPUT_DATA *d, MYSQL_ROW *row )
{
	int i, f;
	ENT("call_spoolers");
	// Iterate throgh the output destinations
	// find formatter for this output destination
	// Call pre-header for this output
	for ( i = 0; i < MAX_SPOOL_FILE; i++ )
	{
		if ( spooler[i].enabled && spooler[i].fp != NULL )
		{
			f = spooler[i].formatter_no;
			if ( f >= 0 )
			{
				d->output_file_fp = spooler[i].fp;		// xyzzy - why - instead of one time during initialization
				output_fmt_f[f].func ( f_act, d, row );
			}

		}
	}
	EX();
}

/*
xyzzy
answer the following questions
1. if I set do_spool - then do I get the correct stuff in the spool structure
2. where to I match the output formats to the destinations
3. What are the structures - write a comment at this point
4. What is the flow between all of them
5. What are the dead fuctions spool_*?
6. How do I Implemnt paging
*/
int do_select (char *arg, char *raw_line)
{
	MYSQL_RES *res;
	MYSQL_ROW row;
	MYSQL_FIELD *field;
	MYSQL_FIELD *all_fields[100];
	int field_len[100];
	char * query;
	int num_fields, i, err_code, j, rl;
	int lines;
	int columns;
	int w;
	int is_tty = 0;
	ENT("do_select");
	PRMs("arg",arg);

	if ( ! is_connected )
	{
		fprintf ( stderr, "Error (3): You must connect to the database first\n" );
		EX();
		return 1;
	}

	/* If we are talking to a terminal we need to keep from wraping and making the output unreadable.
	   Pick up the size of the terminal.  If not a terminal then use the big size for pages.
	 */
	if ( is_stdin ( ) )
	{
		is_tty = 1;
		getTerminalSize ( &lines, &columns ); // used for paging and TTY output of selects.
	}
	else
	{
		lines = 100;			// Default page size.
		columns = MAX_BUF;
	}

	AT();
	query = (char *)x_malloc( 12 + strlen(arg) );
	AT();
	sprintf ( query, "select %s", arg );
	AT();
	PRMs("query",query);
//xyzzy-88
	if ( err_code = mysql_real_query(&mysql,query,(unsigned int)strlen(query)) )
	{
		AT();
		report_error(err_code);
	}
	AT();
	res = mysql_store_result(&mysql);
	AT();
	if ( res == NULL )
	{
		// Xyzzy - check for errors - reparse and use spelling correcting
		fprintf ( stderr, "Error (68163746): Line No %d: Invalid query.\n", LINE_NO );
	}
	else
	{

		AT();
		num_fields = mysql_num_fields(res);

		/* Go through data to look at size for output sizing */
		OUTPUT_DATA *d = new_output_data (num_fields, columns);

		for ( i = 0; i < num_fields; i++ )
		{
			field_len[i] = 1;
		}
		for( j = 0; row = mysql_fetch_row(res); j++)
		{
			if ( j == 0 )
			{
				for ( i = 0; i < num_fields && w < columns; i++ )
				{
					if(all_fields[i] = field = mysql_fetch_field(res))
					{
						d->hdr_widths[i] = strlen( field->name );
						d->headers[i] = x_dupstr( field->name );
					}
				}
			}
			for ( i = 0; i < num_fields; i++ )
			{
				rl = strlen ( (row[i]?row[i]:NULL_STR) );						// MySQL_Str_Of( ..., field->type )
				field_len[i] = ( field_len[i] < rl ) ? rl : field_len[i];
			}
		}
		for ( i = 0; i < num_fields; i++ )
		{
			d->col_widths[i] = field_len[i];
		}

		// xyzzy - call the ouptuter to do init, header
		call_spoolers ( F_ACT_INIT, d, NULL );
		call_spoolers ( F_ACT_HDR, d, NULL );
		call_spoolers ( F_ACT_HDR_LINE, d, NULL );

		/* Back to the top - now go through it to display it. */
		mysql_data_seek(res, 0);
		for( j = 0; row = mysql_fetch_row(res); j++)
		{
			call_spoolers ( F_ACT_ROW, d, &row );
		}
		call_spoolers ( F_ACT_FTR, d, NULL );
		mysql_free_result(res); 
		call_spoolers ( F_ACT_CLEANUP, d, NULL );
	}
	x_free(query);

	EX();
	return 1;
}
int do_sh (char *arg, char *raw_line)
{
	ENT("do_sh");
	system ( arg );
	EX();
	return (1);
}
int do_set (char *arg, char *raw_line)
{
	char to_set[MAX_BUF];
	char value[MAX_BUF];
	int i, err;
	ENT("do_set");
	if ( strlen (arg) > MAX_BUF )
	{
		fprintf ( stderr, "Error (87383627): Invalid set command.  Too Long\n" );
		EX();
		return 0;
	}
	AT();
	sscanf ( arg, "%s%s", to_set, value );
	set_nv_pair ( to_set, value );
	EX();
	return 1;
}
int do_use (char *arg, char *raw_line)
{
	int err_code;
	ENT("do_use");
	if ( err_code = mysql_select_db(&mysql, arg) )
	{
		report_error(err_code);
	}
	EX();
	return (1);
}
int do_connect (char *arg, char *raw_line)
{
	char un[MAX_LOGIN_BUF], pw[MAX_LOGIN_BUF], db[MAX_LOGIN_BUF], host[MAX_LOGIN_BUF];
	ENT("do_connect");
	PRMs("arg",arg);
	
	parse_connect ( arg, host, un, pw, db );
	PRMs("host",host);
	PRMs("un",un);
	PRMs("pw",pw);
	PRMs("db",db);
	if ( is_connected )
	{
		do_disconnect (arg,raw_line);
	}
	if ( ! mysql_real_connect(&mysql,host,un,pw,db,0,NULL,0) )
	{
		fprintf ( stderr, "Error (28539132): Unable to connect as %s to %s on %s\n", un, db, host );
		exit(1);
	}
	is_connected = 1;
	EX();
	return (1);
}
int do_help (char *arg, char *raw_line)
{
	int i;
	ENT("do_help");
	fprintf ( stdout, "Steamboat. (C) Copyright 2009-2010 Philip Schlump.\n" );
	fprintf ( stdout, "sb is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License.\n" );
	for ( i = 0; commands[i].name; i++ )
	{
		fprintf ( stdout, "%s\n", commands[i].doc );
	}
	EX();
	return (1);
}
int do_save (char *arg, char *raw_line)
{
	ENT("do_save");
	// xyzzy
	EX();
	return (1);
}
int do_load (char *arg, char *raw_line)
{
	ENT("do_load");
	// xyzzy
	EX();
	return (1);
}
int do_run (char *arg, char *raw_line)
{
	FILE *new_fp;
	char path[MAX_BUF];
	ENT("do_run");
	arg = find_file_path ( arg, SQL_PATH, path ); // will dynamically allocate copy of fn.
	AT();
	if ( arg )
	{
		AT();
		if ( ( new_fp = fopen ( arg, "r" ) ) == NULL )
		{
			fprintf ( stderr, "Error (76588600): Unable to open %s for input.\n", arg );
			stk_dump_file_stack ( stderr );
		}
		stk_push_file ( arg, 0, path, new_fp, 0 /* is stdin:false */);
		AT();
		i_fp = new_fp;
		LINE_NO = 0;
		AT();
		stk_set_stack_line_no( LINE_NO );
		x_free ( arg );
	}
	else
	{
		fprintf ( stderr, "Error (47005641): Missing file [%s]\n", arg );
	}
	EX();
	return (1);
}
int do_quit (char *arg, char *raw_line)
{
	ENT("do_quit");
	done = 1;
	EX();
	return 1;
}
int do_comment (char *arg, char *raw_line)
{
	ENT("do_comment");
	EX();
	return 1;
}
int do_history (char *arg, char *raw_line)
{
	HIST_ENTRY **list;
	int i;
	ENT("do_history");

	list = history_list ();
	if (list)
	{
		for (i = 0; list[i]; i++)
			fprintf (stderr, "%d: %s\r\n", i, list[i]->line);
	}
	EX();
}
int do_disconnect (char *arg, char *raw_line)
{
	if ( is_connected )
	{
		mysql_close(&mysql);
	}
	return 1;
}

int do_insert (char *arg, char *raw_line)
{
	int rv;
	ENT("xx");
	rv = run_sql ( "insert", arg );
	EX();
	return rv;
}
int do_update  (char *arg, char *raw_line)
{
	int rv;
	ENT("xx");
	rv = run_sql ( "update", arg );
	EX();
	return rv;
}
int do_delete  (char *arg, char *raw_line)
{
	int rv;
	ENT("xx");
	rv = run_sql ( "delete", arg );
	EX();
	return rv;
}
int do_create  (char *arg, char *raw_line)
{
	int rv;
	ENT("xx");
	rv = run_sql ( "create", arg );
	EX();
	return rv;
}
int do_drop  (char *arg, char *raw_line)
{
	int rv;
	ENT("xx");
	rv = run_sql ( "drop", arg );
	EX();
	return rv;
}
int do_show  (char *arg, char *raw_line)
{
	int rv;
	ENT("xx");
	rv = run_sql ( "show", arg );
	EX();
	return rv;
}
int do_desc  (char *arg, char *raw_line)
{
	// xyzzy - 
	return 0;
}
int do_echo  (char *arg, char *raw_line)
{
	printf ( "%s\n", arg );
	return 0;
}


void empty_trace_pat()
{
	int i;
	ENT("empty_trace_pat");
	for ( i = 0; i < n_trace_pat; i++ )
	{
		x_free( trace_pat[i] );
		trace_pat[i] = NULL;
	}
	EX();
}
int do_trace  (char *arg, char *raw_line)
{
	int i;
	ENT("do_trace");
	PRMs("arg",arg);
	if ( strcmp ( arg, "-" ) == 0 )
	{
		empty_trace_pat();
	}
	else if ( n_trace_pat < MAX_TRACE_PAT )
	{
		TRACE = 1;		// # 000004. chagne do_trace to turn trace on as well
		trace_pat[n_trace_pat++] = x_dupstr( arg );
		AT();
	}
	EX();
	return 1;
}




int do_spool (char *arg, char *raw_line)
{
	char *s;
	int n_sp;
	FILE *fp;
	int i;
	ENT("do_spool");

	SPOOL_ON = 1;
	if ( strcmp ( arg, "alloff" ) == 0 )
	{
		for ( i = 1; i < MAX_SPOOL_FILE; i++ )	// skip [0] as it is the terminal input/output (or input file)
		{
			if ( spooler[i].fp )
			{
				close_spool_file ( i );
			}
		}
	}
	else if ( strncmp ( arg, "-", 1 ) == 0 )
	{
		for ( i = 1; i < MAX_SPOOL_FILE; i++ )
		{
			if ( strcmp ( arg+1, spooler[i].fn ) == 0 )
			{
				close_spool_file ( i );
				break;
			}
		}

	}
	else
	{
		if ( ( n_sp = spool_file_availabel() ) == -1 )
		{
			fprintf ( stderr, "Erorr (44): No spool file is availabel. More than %d files in use\n", MAX_SPOOL_FILE );
			EX();
			return 0;
		}
		s = get_output_format ( arg );
		open_spool_file ( arg, "txt", 0 );
	}
	EX();
	return (1);
}

#ifdef PUSH_CMD
#include push_g.inc
#endif


/*
Undefined symbols:
  "_mysql_num_fields", referenced from:
      _do_select in sb.o
*/
